
#include "BaseConvert.h"
#include "../iface/debug.h"
#include "../iface/SSLInterface.h"
#include "../aprtools/APRPRNG.h"
#include <openssl/evp.h>
#include "Hash.h"

#include "../aprtools/APRPRNG.h"

/** Makes an ID in base 64. Freeing the resulting ID string is the
 * responsibility of the caller.
 * 
 * @param bytes Number of bytes to use (8-bit)
 * 
 * @return A string in base64 encoding these bytes. This string must
 * be eventually freed by the caller. */
char *Hash::makeBase64ID(int bytes)
{
   APRPRNG aprrng;
   char *ranbuf = aprrng.getRandomBytes(bytes);
   char *buf64 = BaseConvert::convertBytesToBase64(ranbuf, bytes);
   free(ranbuf);
   return buf64;
}

/** Make a hexadecimal ID. Freeing the resulting ID string is the
 * responsibility of the caller.
 * 
 * @param bytes Number of bytes to use (8-bit)
 * 
 * @return A string in hexadecimal encoding these bytes. This string
 * must be eventually freed by the caller. */
char *Hash::makeHexadecimalID(int bytes)
{
   APRPRNG aprrng;
   char *ranbuf = aprrng.getRandomBytes(bytes);
   char *bufhex = BaseConvert::convertBytesToHexadecimal(ranbuf, bytes);
   free(ranbuf);
   return bufhex;
}

/** Convert hash string to colon hex format. The returned string must
 * be freed by the caller.
 * 
 * @param inputstr Input string as a readable hexadecimal hash without
 * colon spearation.
 * 
 * @return The new colon separated hexadecimal hash. */
char *Hash::convertToColonHex(const char *inputstr)
{
   // I think we are assuming that all hashes give values that are
   // multiples of 8 bits, and that the zeroes are represented.
   if (inputstr == NULL) return NULL;
   char *newstring = (char *)malloc(sizeof(char)*(strlen(inputstr) + (strlen(inputstr)/2+1)));
   int j = 0;
   for (int i = 0; i < strlen(inputstr); i++)
   {
      newstring[j] = inputstr[i];
      if (i & 1)
      {
         j++;
         newstring[j] = ':';
      }
      j++;
   }
   newstring[--j] = '\0';
   return newstring;
}

/** Get the MD5 hash of the data given in the buffer.  This must be
 * freed by the caller.
 * 
 * @param data The buffer of data to be hashed.
 * 
 * @param length The length of the buffer of data.
 * 
 * @param prefix An optional prefix to be added to the hexadecimal
 * hash string.
 * 
 * @return A string containing a hexadecimal representation of the MD5
 * hash. This must be freed by the caller. */
char *Hash::getHexMD5Hash(const char *data, int length, const char *prefix)
{
   int pref_len = (prefix) ? strlen(prefix) : 0;
   int mdlen = getBinaryMD5Hash(NULL, 0, NULL);
   char *md5hash = (char *)malloc(sizeof(char)*(mdlen*2+1+pref_len));
   unsigned char digest[EVP_MAX_MD_SIZE];
   if (prefix)
      strcpy(md5hash, prefix);
   else
      md5hash[0] = '\0';
   Hash::getBinaryMD5Hash(data, length, digest);
   for (int i = 0; i < mdlen; i++)
   {
      char hex[3];
      sprintf(hex, "%02x", digest[i]);
      strcat(md5hash, hex);
   }
   return md5hash;
}

/** Get the SHA1 hash of the data given in the buffer. This must be
 * freed by the caller.
 * 
 * @param data The buffer of data to be hashed.
 * 
 * @param length The length of the buffer of data.
 * 
 * @param prefix An optional prefix to be added to the hexadecimal
 * hash string.
 * 
 * @return A string containing a hexadecimal representation of the
 * SHA1 hash. This must be freed by the caller. */
char *Hash::getHexSHA1Hash(const char *data, int length, const char *prefix)
{
   int pref_len = (prefix) ? strlen(prefix) : 0;
   int mdlen = getBinarySHA1Hash(NULL, 0, NULL);
   char *sha1hash = (char *)malloc(sizeof(char)*(mdlen*2+1+pref_len));
   unsigned char digest[EVP_MAX_MD_SIZE];
   if (prefix)
      strcpy(sha1hash, prefix);
   else
      sha1hash[0] = '\0';
   Hash::getBinarySHA1Hash(data, length, digest);
   for (int i = 0; i < mdlen; i++)
   {
      char hex[3];
      sprintf(hex, "%02x", digest[i]);
      strcat(sha1hash, hex);
   }
   return sha1hash;
}

/** Get the SHA256 hash of the data given in the buffer. This must be
 * freed by the caller.
 * 
 * @param data The buffer of data to be hashed.
 * 
 * @param length The length of the buffer of data.
 * 
 * @param prefix An optional prefix to be added to the hexadecimal
 * hash string.
 * 
 * @return A string containing a hexadecimal representation of the
 * SHA256 hash. This must be freed by the caller. */
char *Hash::getHexSHA256Hash(const char *data, int length, const char *prefix)
{
   int pref_len = (prefix) ? strlen(prefix) : 0;
   int mdlen = getBinarySHA256Hash(NULL, 0, NULL);
   char *sha256hash = (char *)malloc(sizeof(char)*(mdlen*2+1+pref_len));
   unsigned char digest[EVP_MAX_MD_SIZE];
   if (prefix)
      strcpy(sha256hash, prefix);
   else
      sha256hash[0] = '\0';
   Hash::getBinarySHA256Hash(data, length, digest);
   for (int i = 0; i < mdlen; i++)
   {
      char hex[3];
      sprintf(hex, "%02x", digest[i]);
      strcat(sha256hash, hex);
   }
   return sha256hash;
}

/** Get a binary MD5 hash of the data in the buffer.
 * 
 * @param data The buffer of data to be hashed.
 * 
 * @param length The length of the buffer of data.
 * 
 * @param hash The array to write the hash binary data into. */
int Hash::getBinaryMD5Hash(const char *data, int length, unsigned char *hash)
{
   const EVP_MD *md5 = EVP_md5();
   if ((data == NULL) || (hash == NULL)) return md5->md_size;
   if (length == 0) errprint("hashing data of zero length");
   EVP_MD_CTX *ctx;
   ctx = EVP_MD_CTX_create();
   EVP_DigestInit_ex(ctx, md5, NULL);
   EVP_DigestUpdate(ctx, data, length);
   EVP_DigestFinal_ex(ctx, hash, NULL);
   EVP_MD_CTX_destroy(ctx);
   SSLCheckError();
   return 0;
}

/** Get a binary SHA-1 hash of the data in the buffer.
 * 
 * @param data The buffer of data to be hashed.
 * 
 * @param length The length of the buffer of data.
 * 
 * @param hash The array to write the hash binary data into.
 * 
 * @return If the data or hash pointers are NULL, return the length of
 * the hash. If instead everything is fine, return zero. */
int Hash::getBinarySHA1Hash(const char *data, int length, unsigned char *hash)
{
   const EVP_MD *sha1 = EVP_sha1();
   if ((data == NULL) || (hash == NULL)) return sha1->md_size;
   if (length == 0) errprint("hashing data of zero length");
   EVP_MD_CTX *ctx;
   ctx = EVP_MD_CTX_create();
   EVP_DigestInit_ex(ctx, sha1, NULL);
   EVP_DigestUpdate(ctx, data, length);
   EVP_DigestFinal_ex(ctx, hash, NULL);
   EVP_MD_CTX_destroy(ctx);
   SSLCheckError();
   return 0;
}

/** Get a binary SHA-256 hash of the data in the buffer.
 * 
 * @param data The buffer of data to be hashed.
 * 
 * @param length The length of the buffer of data.
 * 
 * @param hash The array to write the hash binary data into.
 * 
 * @return If the data or hash pointers are NULL, return the length of
 * the hash. If instead everything is fine, return zero. */
int Hash::getBinarySHA256Hash(const char *data, int length, unsigned char *hash)
{
   const EVP_MD *sha256 = EVP_sha256();
   if ((data == NULL) || (hash == NULL)) return sha256->md_size;
   if (length == 0) errprint("hashing data of zero length");
   EVP_MD_CTX *ctx;
   ctx = EVP_MD_CTX_create();
   EVP_DigestInit_ex(ctx, sha256, NULL);
   EVP_DigestUpdate(ctx, data, length);
   EVP_DigestFinal_ex(ctx, hash, NULL);
   EVP_MD_CTX_destroy(ctx);
   SSLCheckError();
   return 0;
}

/** Take the exclusive or (XOR) of a group of hashes to yield an
 * overall hash. This must be freed by the caller.
 * 
 * @param hashes A NULL-terminated list of hashes to be XOR'ed.
 * 
 * @return A string containing a hexadecimal representation of the
 * final hash. This must be freed by the caller. */
char *Hash::mergeBlockHashes(const char **hashes)
{
   const static char lookup_h2n[] = {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
                                      -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                      -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
   const static char lookup_n2h[] = { '0', '1', '2', '3', '4', '5', '6', '7',
                                      '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
   int count = 0;
   if (hashes[0] == NULL) return NULL;
   int hashlen = strlen(hashes[0]);
   char *result = (char *)malloc(sizeof(char)*(hashlen+1));
   for (int i = 0; i < hashlen; i++) result[i] = '0';
   result[hashlen] = '\0';
   while (hashes[count] != NULL)
   {
      for (int i = 0; i < hashlen; i++)
      {
         int char1 = result[i];
         int char2 = (hashes[count])[i];
         int chr1_conv = (char1 < '0') ? -1 : ((char1 >= ('0' + 64)) ? -1 : lookup_h2n[char1 - '0']);
         int chr2_conv = (char2 < '0') ? -1 : ((char2 >= ('0' + 64)) ? -1 : lookup_h2n[char2 - '0']);
         if ((chr1_conv < 0) || (chr2_conv < 0))
         {
            errprint("invalid characters in hash value");
            return NULL;
         }
         result[i] = lookup_n2h[chr1_conv ^ chr2_conv];
      }
      count++;
   }
   return result;
}
